/*
 * Copyright (C) 2008,2009,2010,2011  Paulo Cesar Pereira de Andrade.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Authors:
 *	Paulo Cesar Pereira de Andrade
 */

#include "exl.h"

#include <stdarg.h>
#include <stdlib.h>

/*
 * Prototypes
 */
static void
message(evector_t *name, eint32_t line, const char *prefix,
	const char *format, va_list ap);

static eint32_t
eprintf(const char *format, ...);

static eint32_t
evprintf(const char *format, va_list ap);

/*
 * Implementation
 */
void
eerror(const char *format, ...)
{
    va_list	ap;

    va_start(ap, format);
    message(null, 0, "error", format, ap);
    va_end(ap);
    exit(-1);
}

void
eabort(eint8_t *code, const char *string)
{
    eint32_t		 i;
    evalue_t		*v;
    evector_t		*func;
    evector_t		*name;
    eint32_t		 line;
    static ebool_t	 aborting;

    if (aborting)
	abort();

    aborting = true;
    ewrite(std_error, "error: unhandled '", 18);
    ewrite(std_error, (eobject_t)string, strlen(string));
    ewrite(std_error, "' exception\n", 12);

    (void)eget_note(code, &func, &name, &line);
    eprintf("#0 %p:%d: %p\n", name, line, func);
    /* search remaining stack frames */
    for (v = thread_self->ebp, i = 0; v > thread_self->ess + 1; --v) {
	if (v->t == t_void && v->v.o) {
	    --v;
	    if (v->t == t_void && v->v.o) {
		++i;
		eget_note(v->v.o, &func, &name, &line);
		/* may have reached a "constructed" thread frame */
		if (name == null)
		    break;
		eprintf("#%d %p:%d: %p\n", i, name, line, func);
	    }
	}
    }

    eflush(std_error);
    exit(-1);
}

void
eread_error(eobject_t list, const char *format, ...)
{
    va_list	ap;

    va_start(ap, format);
    message(input->name, input->line, "error", format, ap);
    va_end(ap);
    exit(-1);
}

void
eparse_error(eobject_t list, const char *format, ...)
{
    va_list	 ap;
    eentry_t	*entry;

    va_start(ap, format);
    if ((entry = eannotation(list)))
	message(ecdr(entry->value), eget_integer(ecar(entry->value)), "error",
		format, ap);
    else
	message(null, 0, "error", format, ap);
    va_end(ap);
    exit(-1);
}

void
enote_error(east_node_t *node, const char *format, ...)
{
    va_list	 ap;

    va_start(ap, format);
    message(node->cnote.name, node->cnote.line, "error", format, ap);
    va_end(ap);
    exit(-1);
}

void
ewarn(const char *format, ...)
{
    va_list	ap;

    va_start(ap, format);
    message(null, 0, "warning", format, ap);
    va_end(ap);
}

void
eread_warn(eobject_t list, const char *format, ...)
{
    va_list	ap;

    va_start(ap, format);
    message(input->name, input->line, "warning", format, ap);
    va_end(ap);
}

void
eparse_warn(eobject_t list, const char *format, ...)
{
    va_list	 ap;
    eentry_t	*entry;

    va_start(ap, format);
    if ((entry = eannotation(list)))
	message(ecdr(entry->value), eget_integer(ecar(entry->value)), "warning",
		format, ap);
    else
	message(null, 0, "warning", format, ap);
    va_end(ap);
}

void
enote_warn(east_node_t *node, const char *format, ...)
{
    va_list	 ap;

    va_start(ap, format);
    message(node->cnote.name, node->cnote.line, "warning", format, ap);
    va_end(ap);
}

static void
message(evector_t *name, eint32_t line, const char *prefix,
	const char *format, va_list ap)
{
    if (line)
	eprintf("%p:%d: ", name, line);
    eprintf("%s: ", prefix);
    evprintf(format, ap);
    eputc(std_error, '\n');

    /* FIXME should not be required... */
    eflush(std_error);
}

eint32_t
eprintf(const char *format, ...)
{
    va_list	ap;
    eint32_t	result;

    va_start(ap, format);
    result = evprintf(format, ap);
    va_end(ap);

    return (result);
}

eint32_t
evprintf(const char *format, va_list ap)
{
    int		 i;
    char	*s;
    eformat_t	 fmt;
    etype_t	 type;
    char	*f, *t;
    eobject_t	 object;
    eint32_t	 length;
    eint32_t	 result;
    evector_t	*vector;

    result = length = 0;
    memset(&fmt, 0, sizeof(eformat_t));
    fmt.radix = 10;
    f = t = (char *)format;
    while (*t) {
	if (*t == '%') {
	    ewrite(std_error, f, t - f);
	    result += t - f;
	    switch (*++t) {
		case '%':
		    eputc(std_error, '%');
		    break;
		case 'c':
		    i = va_arg(ap, int);
		    result += eputc(std_error, i);
		    break;
		case 'd':
		    i = va_arg(ap, int);
		    result += eprint_int(std_error, &fmt, i);
		    break;
		case 's':
		    s = va_arg(ap, char*);
		    length = strlen(s);
		    ewrite(std_error, s, length);
		    result += length;
		    break;
		case 'p':
		default:
		    object = va_arg(ap, eobject_t);
		    /* FIXME this function can be called from
		     * either parsing or printing vm error messages,
		     * but, the vm doesn't support fixnum objects,
		     * and doesn't allow calling eobject_type_novm(),
		     * but, eobject_type() will crash for fixnum
		     * arguments... */
		    if (efixnum_p(object))
			type = t_fixnum;
		    else
			type = eobject_type(object) & ~t_const;
		    switch (type) {
			case t_void:
			    ewrite(std_error, "null", 4);
			    break;
			case t_fixnum:
			    length = eprint_int(std_error, &fmt,
						eget_fixnum(object));
			    break;
			case t_int:
			    length = eprint_int(std_error, &fmt,
						*(eint_t *)object);
			    break;
			case t_string:
			    vector = object;
			    length = vector->length;
			    ewrite(std_error, vector->v.obj, length);
			    break;
			default:
			    length = eprint_ptr(std_error, &fmt, object);
			    break;
		    }
		    result += length;
		    break;
	    }
	    t++;
	    f = t;
	}
	else
	    t++;
    }
    ewrite(std_error, f, t - f);

    return (result + t - f);
}
